/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pk.ecompuefs.micc.dfs.master;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import pk.ecompuefs.micc.dfs.util.User;

/**
 *
 * @author emanuel
 */
public class Master extends Thread {

    private static ServerSocket sserver;
    private static ServerSocket cserver;
    private static Socket slave;
    private Socket client;
    private static DistributedIndexTable indextable;
    private static LoadBalancer balancer;
    private static List<User> USERS_LIST;
    private DataInputStream dis;

    public Master(Socket socket) {
        this.client = socket;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        indextable = new DistributedIndexTable();
        balancer = new LoadBalancer();
        
       
        try {
            loadUsers();
            // Cria um ServerSocket na porta 4010 para atender aos Slaves
            sserver = new ServerSocket(4010);
            log("Servidor Master Iniciado na Porta 4010. Aguardando Conexões...");
            cserver = new ServerSocket(4011);
            
            listeningSlaves();
            
        } catch (IOException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    @Override
    public void run() {
        try {
            log("Client " + client.getInetAddress() + " em execução.");
       //     sendSlavesList();
            dis = new DataInputStream(client.getInputStream());
            int requestCode = 0;

            while (!client.isClosed()) {
                log("Aguardando Requisições...");
                requestCode = dis.readInt();
                System.out.println(requestCode);
                
                switch(requestCode) {
                    
                    case 10 : 
                        log("FTP Client " + client.getInetAddress() + " Desconectou");
                        break;
                        
                    case 12:
                        log("Requisição de Login. Cliente " + client.getInetAddress());
                        login();
                        client.close();
                        log("Cliente " + client.getInetAddress() + " redirecionado para o Slave " + indextable.getSlaves().get(0));
                        break;
                    case 16 :
                        log("Requisição de Registro. Cliente " + client.getInetAddress());
                        register();
                        break;
                        
                    default : log("Requisição não Identificada");
                        break;
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private static void log(String msgm) {
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
        System.out.println(sdf.format(new Date()).toUpperCase() + " => " + msgm);
    }

    private void sendSlavesList() {
        try {
            ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream());
            oos.writeObject(indextable.getSlaves());
            oos.flush();
           
        } catch (IOException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    private static void listeningSlaves() {
        try {
            while(true){
            sserver.setSoTimeout(5000);
            Socket sslave = sserver.accept();
            log("Conexão com o SlaveServer " + sslave.getInetAddress());
            indextable.addSlaveEntry(sslave.getInetAddress().toString());
            balancer.addToRank(sslave.getInetAddress().toString());
            }
        } catch (SocketTimeoutException ex) {
            listeningClients();
        } catch (SocketException ex) {
            
        } catch (IOException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    private static void listeningClients() {
        try {
            cserver.setSoTimeout(5000);
            while(true){
            Socket sclient = cserver.accept();
            log("Nova conexão com o client " + sclient.getInetAddress());
            Thread threadClient = new Master(sclient);
            threadClient.start();
            }
        
        } catch (SocketTimeoutException ex) {
            listeningSlaves();
        } catch (SocketException ex) {
            listeningSlaves();
        } catch (IOException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }
        
    }
    
    /**
     * Método Utilizado para enviar códigos de Resposta ao Cliente
     * @param code 
     */

    public void sender(int code) {
        DataOutputStream output;
        try {
            output = new DataOutputStream(client.getOutputStream());
            output.writeInt(code);
            output.flush();
        } catch (IOException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
    
    /**
     * 
     * @param user
     * @return ret
     */

    private int authentication(User user) {
        int ret = 0;
        for (int s = 0; s < USERS_LIST.size(); s++) {
            if (user.getEmail().equals(USERS_LIST.get(s).getEmail())) {
                if (user.getPassword().equals(USERS_LIST.get(s).getPassword())) {                   
                   
                    ret = 1;
                    
                    break;
                } else {
                    ret = 2;
                    break;
                }

            } else {
            }
        }

        return ret;

    }
    
    
        /**
     * Método para Adicionar usuario
     * @param user 
     */

    public void addUser(User user) {
        USERS_LIST.add(user);
    }
    
    private void login() {
        try {
            ObjectInputStream ois = new ObjectInputStream(client.getInputStream());
            User user = null;
            try {
                user = (User) ois.readObject();
                System.out.println(user.getEmail());
              //  System.out.println(user.getPassword());
              //  System.out.println(authentication(user));

                if (authentication(user) == 1) {
                    System.out.println("Usuario Logado");                    
                    sender(13);
                    sendSlavesList();
                    
                } else if (authentication(user) == 2) {
                    System.out.println("Senha Errada");
                    sender(14);
                    
                } else {
                    System.out.println("E-mail não cadastrado");
                    sender(15);
                }
                
             //   showUsers();
                
            } catch (ClassNotFoundException e) {
            }
        } catch (IOException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * Método para registrar um usuário no Servidor
     */

    private void register() {
        try {
            ObjectInputStream ois = new ObjectInputStream(client.getInputStream());
            User user = (User) ois.readObject();
            System.out.println(authentication(user));
            System.out.println(user.getEmail());
            System.out.println(user.getPassword());
            if (authentication(user) == 0) {
                addUser(user);
                saveUSERS_LIST();               
                sender(17);
            } else {
                System.out.println("email ja existe");
                sender(18);
            }

//            showUsers();
        } catch (IOException | ClassNotFoundException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }


    }
    
    /**
     * Ler a lista de usuários registrados a partir de um arquivo, caso esse arquivo não exista ele cria uma Lista
     * @throws IOException
     * @throws ClassNotFoundException 
     */
    
    private static void loadUsers() throws IOException, ClassNotFoundException {
        
        File file = new File("USERS_LIST.obj");
            if(!file.exists()){
                USERS_LIST = new ArrayList<>();
                System.out.println("Lista de Usuários Criada...");
            }
                else{
                    try (ObjectInputStream objectIn = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
                        USERS_LIST = (ArrayList<User>)objectIn.readObject();
                        System.out.println("Carregando Lista de Usuários...");
                    }
                }
        
    }
    
        /**
     * Salva a lista de usuarios em um arquivo
     * @throws IOException 
     */
    
    private static void saveUSERS_LIST() throws IOException {
        
        File file = new File("USERS_LIST.obj");        
        ObjectOutputStream objectOut;              
        try {
            objectOut = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
            objectOut.writeObject(USERS_LIST);  
            objectOut.flush();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }
               
    }
}
